#include "state.h"

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "snake_utils.h"

#define INITIAL_BUFFER_SIZE 100

/* Helper function definitions */
static void set_board_at(game_state_t* state, unsigned int row, unsigned int col, char ch);
static bool is_tail(char c);
static bool is_head(char c);
static bool is_snake(char c);
static char body_to_tail(char c);
static char head_to_body(char c);
static unsigned int get_next_row(unsigned int cur_row, char c);
static unsigned int get_next_col(unsigned int cur_col, char c);
static void find_head(game_state_t* state, unsigned int snum);
static char next_square(game_state_t* state, unsigned int snum);
static void update_tail(game_state_t* state, unsigned int snum);
static void update_head(game_state_t* state, unsigned int snum);

/* Task 1 */
game_state_t* create_default_state() {
  // TODO: Implement this function.
  /* 这些应该存放在堆里，因为如果存放在栈里，那么当函数执行完毕时，都会丢失 */
  game_state_t* gs = (game_state_t*) malloc(sizeof(game_state_t));
  if (gs == NULL) {
    return NULL;
  }
  /* 初始化两个指针来指向snakes和board,更直观一点 */
  /* 应该在分配内存后在创建新指针，因为如果之后用这个指针变量申请内存的话，它们会指向新的位置。 */
//   snake_t* snakes = gs->snakes;
//   char** board = gs->board;
  gs->num_rows = 18;
  gs->num_snakes = 1;
  /* 分配内存，失败后释放已经分配的内存，然后返回。 */
  gs->board = (char**) malloc(gs->num_rows * sizeof(char*));
  if (gs->board == NULL) {
    free(gs);
    return NULL;
  }
  char** board = gs->board;
  gs->snakes = (snake_t*) malloc(sizeof(snake_t));
  if (gs->snakes == NULL) {
    free(board);
    free(gs);
    return NULL;
  }
  snake_t* snakes = gs->snakes;

  /* 给每一行分配内存，如果分配失败了，就将之前分配的释放。 */
  for (int i = 0; i < gs->num_rows; i++) {
    *(board + i) = (char*) malloc(21 * sizeof(char));
    if (*(board + i) == NULL) {
        int rollback = i - 1;
        while (rollback >= 0) {
            free(*(board + rollback));
            rollback--;
        }
        free(board);
        free(snakes);
        free(gs);
        return NULL;
    }
  }

  strcpy(*board, "####################");
  for (int i = 1; i < gs->num_rows - 1; i++) {
    strcpy(*(board + i), "#                  #");
  }
  strcpy(*(board + gs->num_rows - 1), "####################");
  
  /* 默认状态蛇的头的位置是2排4列，尾的位置是2排2列 */
  snakes->tail_row = 2;
  snakes->tail_col = 2;
  snakes->head_row = 2;
  snakes->head_col = 4;
  snakes->live = true;

  strcpy(*(board + 2), "# d>D    *         #");
  return gs;
}

/* Task 2 */
void free_state(game_state_t* state) {
  // TODO: Implement this function.
  char** board = state->board;
  for (int i = 0; i < state->num_rows; i++) {
    free(*(board + i));
  }
  free(board);
  free(state->snakes);
  free(state);
  return;
}

/* Task 3 */
void print_board(game_state_t* state, FILE* fp) {
  // TODO: Implement this function.
  char** board = state->board;
  for (int i = 0; i < state->num_rows; i++) {
    fprintf(fp, "%s\n", *(board + i));
  }
  return;
}

/*
  Saves the current state into filename. Does not modify the state object.
  (already implemented for you).
*/
void save_board(game_state_t* state, char* filename) {
  FILE* f = fopen(filename, "w");
  print_board(state, f);
  fclose(f);
}

/* Task 4.1 */

/*
  Helper function to get a character from the board
  (already implemented for you).
*/
char get_board_at(game_state_t* state, unsigned int row, unsigned int col) {
  return state->board[row][col];
}

/*
  Helper function to set a character on the board
  (already implemented for you).
*/
static void set_board_at(game_state_t* state, unsigned int row, unsigned int col, char ch) {
  state->board[row][col] = ch;
}

/*
  Returns true if c is part of the snake's tail.
  The snake consists of these characters: "wasd"
  Returns false otherwise.
*/
static bool is_tail(char c) {
  // TODO: Implement this function.
  switch (c) {
    case 'w':
    case 'a':
    case 's':
    case 'd':
        return true;
    default:
        return false;
  }
}

/*
  Returns true if c is part of the snake's head.
  The snake consists of these characters: "WASDx"
  Returns false otherwise.
*/
static bool is_head(char c) {
  // TODO: Implement this function.
  switch (c) {
    case 'W':
    case 'A':
    case 'S':
    case 'D':
    case 'x':
        return true;
    default:
        return false;
  }
}

/*
  Returns true if c is part of the snake.
  The snake consists of these characters: "wasd^<v>WASDx"
*/
static bool is_snake(char c) {
  // TODO: Implement this function.
  const char* snakePart = "wasd^<v>WASDx";
  return strchr(snakePart, c) != NULL;
}

/*
  Converts a character in the snake's body ("^<v>")
  to the matching character representing the snake's
  tail ("wasd").
*/
static char body_to_tail(char c) {
  // TODO: Implement this function.
  switch (c) {
    case '^':
        return 'w';
    case '<':
        return 'a';
    case 'v':
        return 's';
    case '>':
        return 'd';
  }
  return '?';
}

/*
  Converts a character in the snake's head ("WASD")
  to the matching character representing the snake's
  body ("^<v>").
*/
static char head_to_body(char c) {
  // TODO: Implement this function.
  switch (c) {
    case 'W':
        return '^';
    case 'A':
        return '<';
    case 'S':
        return 'v';
    case 'D':
        return '>';
  }
  return '?';
}

/*
  Returns cur_row + 1 if c is 'v' or 's' or 'S'.
  Returns cur_row - 1 if c is '^' or 'w' or 'W'.
  Returns cur_row otherwise.
*/
static unsigned int get_next_row(unsigned int cur_row, char c) {
  // TODO: Implement this function.
  switch (c) {
    case 'v':
    case 's':
    case 'S':
        cur_row += 1;
        break;
    case '^':
    case 'w':
    case 'W':
        cur_row -= 1;
        break;
  }
  return cur_row;
}

/*
  Returns cur_col + 1 if c is '>' or 'd' or 'D'.
  Returns cur_col - 1 if c is '<' or 'a' or 'A'.
  Returns cur_col otherwise.
*/
static unsigned int get_next_col(unsigned int cur_col, char c) {
  // TODO: Implement this function.
  switch (c) {
      case '>':
      case 'd':
      case 'D':
        cur_col += 1;
        break;
      case '<':
      case 'a':
      case 'A':
        cur_col -= 1;
        break;
  }
  return cur_col;
}

/*
  Task 4.2

  Helper function for update_state. Return the character in the cell the snake is moving into.

  This function should not modify anything.
*/
static char next_square(game_state_t* state, unsigned int snum) {
  // TODO: Implement this function.
   /* 得到要更新的snake */
  snake_t* snake = (state->snakes) + snum;
  /* 得到蛇的头部 */
  char head = get_board_at(state, snake->head_row, snake->head_col);
  /* 得到蛇的下一行和下一列 */
  unsigned int nextRow = get_next_row(snake->head_row, head);
  unsigned int nextCol = get_next_col(snake->head_col, head);
  return get_board_at(state, nextRow, nextCol);
}

/*
  Task 4.3

  Helper function for update_state. Update the head...

  ...on the board: add a character where the snake is moving

  ...in the snake struct: update the row and col of the head

  Note that this function ignores food, walls, and snake bodies when moving the head.
*/
static void update_head(game_state_t* state, unsigned int snum) {
  // TODO: Implement this function.
  snake_t* snake = (state->snakes) + snum;
  char head = get_board_at(state, snake->head_row, snake->head_col);
  char htb = head_to_body(head);
  /* 原来的头部变为身体 */
  set_board_at(state, snake->head_row, snake->head_col, htb);
  /* 得到蛇头的下一行和下一列 */
  unsigned int nextRow = get_next_row(snake->head_row, head);
  unsigned int nextCol = get_next_col(snake->head_col, head);
  /* 将蛇的下一个位置设置为蛇 */
  set_board_at(state, nextRow, nextCol, head);
  /* 更新头的位置 */
  snake->head_row = nextRow;
  snake->head_col = nextCol;
  return;
}

/*
  Task 4.4

  Helper function for update_state. Update the tail...

  ...on the board: blank out the current tail, and change the new
  tail from a body character (^<v>) into a tail character (wasd)

  ...in the snake struct: update the row and col of the tail
*/
static void update_tail(game_state_t* state, unsigned int snum) {
  // TODO: Implement this function.
  snake_t* snake = (state->snakes) + snum;
  /* 得到蛇尾 */
  char tail = get_board_at(state, snake->tail_row, snake->tail_col);
  /* 得到蛇尾的下一行和下一列 */
  unsigned int nextRow = get_next_row(snake->tail_row, tail);
  unsigned int nextCol = get_next_col(snake->tail_col, tail);
  char nextTail = get_board_at(state, nextRow, nextCol);
  /* 将身体换成蛇尾 */
  char btt = body_to_tail(nextTail);
  set_board_at(state, nextRow, nextCol, btt);
  /* 将原来的位置留空 */
  set_board_at(state, snake->tail_row, snake->tail_col, ' ');
  /* 更新蛇尾的位置 */
  snake->tail_row = nextRow;
  snake->tail_col = nextCol;
  return;
}

/* Task 4.5 */
void update_state(game_state_t* state, int (*add_food)(game_state_t* state)) {
  // TODO: Implement this function.
  snake_t* snake = state->snakes;
  /* 循环更新每条蛇 */
  for (unsigned int i = 0; i < state->num_snakes; i++, snake += 1) {
    /* 死的蛇不再更新 */
    if (!(snake->live)) {
      continue;
    }
    char nextSquare = next_square(state, i);
    if (is_snake(nextSquare) || nextSquare == '#') {
      /* 将蛇头变为x */
      set_board_at(state, snake->head_row, snake->head_col, 'x');
      /* 蛇死亡 */
      snake->live = false;
      continue;
    }
    /* 如果是*代表水果，蛇的长度增加，只更新头，不更新尾 */
    if (nextSquare == '*') {
      update_head(state, i);
      add_food(state);
    } else {
      update_head(state, i);
      update_tail(state, i);
    }
  }
  return;
}

/* Task 5's Helper Funcion:动态的读取一行 */
char* read_line(FILE* f) {
  /* 以读模式打开文件 */
  size_t bufferSize = 1024;
  /* 读取到的内容存放在这个buffer中 */
  char* buffer = (char*) malloc(bufferSize * sizeof(char));
  if(buffer == NULL) {
    return NULL;
  }
  size_t currentSize = 0;
  while(1) {
    /* 到达文件末尾或读取错误 */
    if (fgets(buffer + currentSize, bufferSize - currentSize, f) == NULL) {
        free(buffer);
        return NULL;
    }
    /* 计算实际读取的字符数 */
    size_t bytesRead = strlen(buffer + currentSize);
    currentSize += bytesRead;
    /* 检测是否读到换行符 */
    if (buffer[currentSize - 1] == '\n') {
      /* 存在换行符说明读取一行完毕 */
      buffer[currentSize - 1] = '\0';
      /* 重新分配适合的大小 */
      char* tmp = realloc(buffer, currentSize);
      if (tmp == NULL) {
        free(buffer);
        return NULL;
      }
      buffer = tmp;
      break;
    }
    /* 如果缓冲区满，增加缓冲区的大小 */
    if (currentSize == bufferSize - 1) {
      bufferSize *= 2;
      char* tmp = realloc(buffer, bufferSize);
      if (tmp == NULL) {
        free(buffer);
        return NULL;
      }
      buffer = tmp;
    }
  }
  return buffer;
}


/* Task 5 */
game_state_t* load_board(char* filename) {
  // TODO: Implement this function.
  /* 如果文件不存在，返回NULL */
  FILE* f = fopen(filename, "r");
  if (f == NULL) {
    return NULL;
  }
  /* 给要创建state分配空间 */
  game_state_t* state = (game_state_t*) malloc(sizeof(game_state_t));
  if (state == NULL) {
    fclose(f);
    return NULL;
  }
  state->num_rows = 0;
  state->num_snakes = 0;
  state->snakes = NULL;
  /* 初始分配的board的行数 */
  size_t bufferSize = 100;
  state->board = (char**) malloc(bufferSize * sizeof(char*));
  if (state->board == NULL) {
    fclose(f);
    free(state);
    return NULL;
  }
//   char** board = state->board;
  size_t i = 0;
  while (1) {
    if(i == bufferSize) {
      /* 如果把分配的大小填满了，则重新分配 */
      bufferSize *= 2;
      /* 注意，这里申请内存应该是大小乘上char*类型值的大小 */
      char** board = realloc(state->board, bufferSize * sizeof(char*));
      if (board == NULL) {
        fclose(f);
        free(state->board);
        free(state);
        return NULL;
      }
      state->board = board;
    }
    /* 读取每一行 */
    *(state->board + i) = read_line(f);
    if(*(state->board + i) == NULL) {
      break;
    }
    state->num_rows++;
    i++;
  }
  fclose(f);
  return state;
}

/*
  Task 6.1

  Helper function for initialize_snakes.
  Given a snake struct with the tail row and col filled in,
  trace through the board to find the head row and col, and
  fill in the head row and col in the struct.
*/
static void find_head(game_state_t* state, unsigned int snum) {
  // TODO: Implement this function.
  unsigned int tail_row = state->snakes[snum].tail_row;
  unsigned int tail_col = state->snakes[snum].tail_col;
  /* 得到蛇尾 */
  char tail = get_board_at(state, tail_row, tail_col);
  /* 得到蛇尾的下一行和下一列 */
  unsigned int nextRow = get_next_row(tail_row, tail);
  unsigned int nextCol = get_next_col(tail_col, tail);
  char next = get_board_at(state, nextRow, nextCol);
  while(1) {
    if (is_head(next)) {
      state->snakes[snum].head_row = nextRow;
      state->snakes[snum].head_col = nextCol;
      break;
    }
    nextRow = get_next_row(nextRow, next);
    nextCol = get_next_col(nextCol, next);
    next = get_board_at(state, nextRow, nextCol);
  }
  return;
}

/* Task 6.2 */
game_state_t* initialize_snakes(game_state_t* state) {
  // TODO: Implement this function.
  state->num_snakes = 0;
  state->snakes = (snake_t*) malloc(sizeof(snake_t));
  if (state->snakes == NULL) {
    return NULL;
  }
  for (unsigned int i = 0; i < state->num_rows; i++) {
    for (unsigned int j = 0; get_board_at(state, i, j) != '\0'; j++) {
        char c = get_board_at(state, i, j);
        if (is_tail(c)) {
            state->num_snakes++;
            state->snakes = realloc(state->snakes, state->num_snakes * sizeof(snake_t));
            if (state->snakes == NULL) {
                free(state->snakes);
                return NULL;
            }
            state->snakes[state->num_snakes - 1].tail_row = i;
            state->snakes[state->num_snakes - 1].tail_col = j;
            state->snakes[state->num_snakes - 1].live = true;
            find_head(state, state->num_snakes - 1);
        }
    }
  }
  return state;
}
